package socket

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"sync"
	"time"
	"wrblog-api-go/app/common/token"
	"wrblog-api-go/config"
	"wrblog-api-go/pkg/mylog"
	"wrblog-api-go/pkg/redis"
	"wrblog-api-go/pkg/utils"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

var wsKey = "socket_key:"            // Redis 心跳前缀
var timeout = config.Conf.Ws.Timeout // 心跳超时时间(分钟)

// clientMap 支持并发，userId -> clientId -> *client
var clientMap = struct {
	sync.RWMutex
	internal map[int64]map[string]*client
}{internal: make(map[int64]map[string]*client)}

type client struct {
	UserId   int64           `json:"userId"`
	ClientId string          `json:"clientId"`
	OS       string          `json:"os"`
	Ws       *websocket.Conn `json:"-"`
	LastHB   time.Time       `json:"lastHeartbeat"`
}

type socketData struct {
	Type    string `json:"type"`
	Content struct {
		ToUserId string `json:"toUserId"`
		Type     string `json:"type"`
		Msg      string `json:"msg"`
	} `json:"content"`
}

// WebSocket 升级器
var upGrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin: func(r *http.Request) bool {
		return true // 需求只要 token 鉴权即可
	},
}

// WebsocketHandler WebSocket入口
func WebsocketHandler(c *gin.Context) {
	userId := token.GetUserId(c)
	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		return
	}
	defer ws.Close()
	// 创建客户端信息
	clientInfo := &client{
		UserId:   userId,
		ClientId: utils.GetUUIDString(),
		OS:       "pc",
		Ws:       ws,
		LastHB:   time.Now(),
	}
	// 添加客户端
	addClient(clientInfo)
	// 关闭时清理
	defer removeClient(clientInfo.UserId, clientInfo.ClientId)
	for {
		if err := receive(ws, clientInfo); err != nil {
			break
		}
	}
}

// 添加客户端
func addClient(c *client) {
	clientMap.Lock()
	defer clientMap.Unlock()
	if _, ok := clientMap.internal[c.UserId]; !ok {
		clientMap.internal[c.UserId] = make(map[string]*client)
	}
	clientMap.internal[c.UserId][c.ClientId] = c
}

// 删除客户端
func removeClient(userId int64, clientId string) {
	clientMap.Lock()
	defer clientMap.Unlock()
	if clients, ok := clientMap.internal[userId]; ok {
		delete(clients, clientId)
		if len(clients) == 0 {
			delete(clientMap.internal, userId)
		}
	}
}

// 接收消息处理
func receive(ws *websocket.Conn, clientInfo *client) error {
	mt, content, err := ws.ReadMessage()
	if err != nil {
		return err
	}

	var data socketData
	if err := json.Unmarshal(content, &data); err != nil {
		mylog.MyLog.Warn("消息解析失败，UserId: %d, Error: %v", clientInfo.UserId, err)
		return err
	}

	switch data.Type {
	case "heartbeat":
		clientInfo.LastHB = time.Now()
		redisKey := fmt.Sprintf("%s%d", wsKey, clientInfo.UserId)
		if err := redis.SetTime(redisKey, clientInfo.ClientId, time.Duration(timeout)*time.Minute); err != nil {
			mylog.MyLog.Warn("Redis 保存心跳失败，UserId: %d, Error: %v", clientInfo.UserId, err)
		}
		// 回复心跳确认
		pong := map[string]any{
			"type": "pong",
			"time": time.Now().Format("2006-01-02 15:04:05"),
		}
		data, _ := json.Marshal(pong)
		clientInfo.Ws.WriteMessage(websocket.TextMessage, data)
		break
	default:
		targetUserId, _ := strconv.ParseInt(data.Content.ToUserId, 10, 64)
		sendToUser(targetUserId, mt, content, clientInfo)
		break
	}
	return nil
}

// 给目标用户推送消息
func sendToUser(toUserId int64, mt int, content []byte, srcClient *client) {
	clientMap.RLock()
	clients, ok := clientMap.internal[toUserId]
	srcClients := clientMap.internal[srcClient.UserId]
	clientMap.RUnlock()

	if !ok || len(clients) == 0 {
		// 目标用户不在线，通知发送者
		for _, c := range srcClients {
			data := socketData{
				Type: "system",
			}
			data.Content.Msg = "目标用户不在线！"
			data.Content.ToUserId = fmt.Sprintf("%d", srcClient.UserId)
			b, _ := json.Marshal(data)
			push(mt, b, c.Ws)
		}
		return
	}

	// 发送给目标用户所有连接
	for _, c := range clients {
		if err := push(mt, content, c.Ws); err != nil {
			mylog.MyLog.Warn("消息发送失败 UserId: %d, Error: %v", toUserId, err)
		}
	}
}

// 推送消息
func push(mt int, content []byte, ws *websocket.Conn) error {
	return ws.WriteMessage(mt, content)
}

// 定时清理心跳超时客户端
func StartHeartbeatChecker() {
	ticker := time.NewTicker(1 * time.Minute)
	go func() {
		for range ticker.C {
			now := time.Now()
			clientMap.Lock()
			for uid, clients := range clientMap.internal {
				for cid, c := range clients {
					if now.Sub(c.LastHB) > time.Duration(timeout)*time.Minute {
						// 关闭连接
						if c.Ws != nil {
							c.Ws.Close()
						}
						delete(clients, cid)
					}
				}
				if len(clients) == 0 {
					delete(clientMap.internal, uid)
				}
			}
			clientMap.Unlock()
		}
	}()
}
